﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using System.Collections.Specialized;
using Chefbook.DataAccess;
using Chefbook.ViewModels;
using Chefbook.DataObjects;
using System.Collections.ObjectModel;
using System.ComponentModel;
using Chefbook.HelperMethods;
using System.Collections;

namespace UnitTests
{
    public static class ChangedEventHelper
    {
        /// <summary>
        /// Asserts that PropertyChanged events were fired for the specified list of properties and only those properties when performing the action.  
        /// If no properties are specified, asserts that no event was fired.
        /// </summary>
        /// <param name="action">The action to perform</param>
        /// <param name="changedObject">The object that should fire a property changed even</param>
        /// <param name="properties"></param>
        public static void AssertPropertyChangedFired(this INotifyPropertyChanged changedObject, Action action, params string[] properties)
        {
            List<string> changedProp = new List<string>();
            changedObject.PropertyChanged += (s, e) => changedProp.Add(e.PropertyName);
            action();
            foreach (var item in properties)
            {
                Assert.Contains(item, changedProp, "Didn't fire specified PropertyChanged");
                changedProp.Remove(item);
            }
            Assert.AreEqual(0, changedProp.Count, "fired a PropertyChanged event that wasn't specified");
        }

        /// <summary>
        /// Asserts that an action fires a CollectionChanged event in the specified collection and returns the event args
        /// </summary>
        /// <param name="action">The action to perform</param>
        /// <param name="collection">The collection that should fire an event</param>
        /// <returns>The NotifyCollectionChangedEventArgs</returns>
        public static NotifyCollectionChangedEventArgs AssertCollectionChangedFiredAndReturnArgs(this INotifyCollectionChanged collection, Action action)
        {
            var args = ReturnCollectionChangedEventArgs(action, collection)[0];
            Assert.IsNotNull(args); 
            return args;
        }

        /// <summary>
        /// Asserts that an action fires a CollectionChanged event in the specified collection and returns the event args
        /// </summary>
        /// <param name="action">The action to perform</param>
        /// <param name="collection">The collection that should fire an event</param>
        /// <param name="n">The number of times</param>
        /// <returns>The NotifyCollectionChangedEventArgs</returns>
        public static List<NotifyCollectionChangedEventArgs> AssertCollectionChangedFiredNTimesAndReturnArgs(this INotifyCollectionChanged collection, Action action, params NotifyCollectionChangedEventArgs[] expectedArgs)
        {
            return ReturnCollectionChangedEventArgs(action, collection);
        }

        public static bool CollectionElementsEqual(IList a, IList b)
        {
            if (a == b)
                return true;
            else if (a == null || b == null)
                return false;
            foreach (var item in a)
            {
                if (!b.Contains(item)) return false;
            }
            foreach (var item in b)
            {
                if (!a.Contains(item)) return false;
            }
            return true;
        }

        /// <summary>
        /// Asserts that an action didn't fire a CollectionChanged event in the specified collection
        /// </summary>
        /// <param name="action">The action to perform</param>
        /// <param name="collection">The collection that shouldn't fire an event</param>
        public static void AssertCollectionChangedDidntFire(this INotifyCollectionChanged collection, Action action)
        {
            NotifyCollectionChangedEventArgs args = ReturnCollectionChangedEventArgs(action, collection)[0];
            Assert.IsNull(args);
        }

        /// <summary>
        /// Function to comparing 2 NotifyCollectionChangedEventArgs
        /// </summary>
        public static Func<NotifyCollectionChangedEventArgs, NotifyCollectionChangedEventArgs, bool> NotifyCollectionChangedEventArgsEqual = (a, b) => CompareNotifyCollectionChangedEventArgs(a, b);

        private static bool CompareNotifyCollectionChangedEventArgs(NotifyCollectionChangedEventArgs a, NotifyCollectionChangedEventArgs b)
        {
            if (a.Action != b.Action)
            { return false; }
            if (!CollectionElementsEqual(a.NewItems, b.NewItems))
            { return false; }
            if (!CollectionElementsEqual(a.OldItems, b.OldItems))
            { return false; }
            return true;
        }
        private static List<NotifyCollectionChangedEventArgs> ReturnCollectionChangedEventArgs(Action action, INotifyCollectionChanged collection)
        {
            List<NotifyCollectionChangedEventArgs> args = new List<NotifyCollectionChangedEventArgs>();
            collection.CollectionChanged += (s, e) => args.Add(e);
            action();
            if (args.Count == 0)
                args.Add(null);
            return args;
        }
    }
}
